#include "Geometry.h"
#include "VectorMath.h"
#include "Basic/GxLibBasic.h"

using namespace GxLibBasic;
using namespace GxLibMath;

namespace RayTracePro
{
	bool RayBBoxIntersection(const BBox & bbox, const Ray& ray, float & tmin, float & tmax)
	{
		float tymin, tymax, tzmin, tzmax;
		if (ray.ReciprocalDirection.x >= 0) 
		{
			tmin = (bbox.Min.x - ray.Origin.x) * ray.ReciprocalDirection.x;
			tmax = (bbox.Max.x - ray.Origin.x) * ray.ReciprocalDirection.x;
		}
		else
		{
			tmin = (bbox.Max.x - ray.Origin.x) * ray.ReciprocalDirection.x;
			tmax = (bbox.Min.x - ray.Origin.x) * ray.ReciprocalDirection.x;
		}
		if (ray.ReciprocalDirection.y >= 0) 
		{
			tymin = (bbox.Min.y - ray.Origin.y) * ray.ReciprocalDirection.y;
			tymax = (bbox.Max.y - ray.Origin.y) * ray.ReciprocalDirection.y;
		}
		else
		{
			tymin = (bbox.Max.y - ray.Origin.y) * ray.ReciprocalDirection.y;
			tymax = (bbox.Min.y - ray.Origin.y) * ray.ReciprocalDirection.y;
		}
		if ( tmin - tymax > Epsilon || tymin - tmax > Epsilon)
			return false;
		if (tymin > tmin)
			tmin = tymin;
		if (tymax < tmax)
			tmax = tymax;
		if (ray.ReciprocalDirection.z >= 0) 
		{
			tzmin = (bbox.Min.z - ray.Origin.z) * ray.ReciprocalDirection.z;
			tzmax = (bbox.Max.z - ray.Origin.z) * ray.ReciprocalDirection.z;
		}
		else
		{
			tzmin = (bbox.Max.z - ray.Origin.z) * ray.ReciprocalDirection.z;
			tzmax = (bbox.Min.z - ray.Origin.z) * ray.ReciprocalDirection.z;
		}
		if ( tmin - tzmax > Epsilon || tzmin - tmax > Epsilon )
			return false;
		if (tzmin > tmin)
			tmin = tzmin;
		if (tzmax < tmax)
			tmax = tzmax;
		tmin = GxLibMath::Max(ray.tMin, tmin);
		tmax = GxLibMath::Min(ray.tMax, tmax);
		return FloatSmallerOrEquals(tmin, tmax);
	}

	SphereGeometry::SphereGeometry(float rad)
	{
		SetRadius(rad);
		this->ComputeIntersection = SphereGeometry_ComputeIntersectionFunc;
		this->ComputeIntersectionPred = SphereGeometry_ComputeIntersectionPredFunc;
		this->Compute_dNdx = SphereGeometry_Compute_dNdxFunc;
	}

	void SphereGeometry::ComputeBBox(BBox & box)
	{
		BBox box1;
		box1.xMax = box1.yMax = box1.zMax = Radius;
		box1.xMin = box1.yMin = box1.zMin = -Radius;
		TransformBBox(box, Transformation, box1);
	}

	void SphereGeometry::ComputeBoundingSpheres(int depth, Array<Vec4, true> & buffer)
	{
		BBox box;
		ComputeBBox(box);
		Vec3 position;
		Vec3::Add(position, box.Max, box.Min);
		Vec3::Scale(position, position, 0.5f);
		Vec4 sphere;
		sphere.x = position.x; buffer[0].y = position.y; buffer[0].z = position.z;
		Vec3 range;
		Vec3::Subtract(range, box.Max, box.Min);
		sphere.w = Radius;
		buffer.Add(sphere);
	}

	void SphereGeometry::SphereGeometry_Compute_dNdxFunc(DifferentialGeometry & inter, const Vec3 & dPdx, const Vec3 & dPdy)
	{
		float s = 1.0f / ((SphereGeometry*)inter.Geometry)->Radius;
		Vec3::Scale(inter.dNdx, dPdx, s);
		Vec3::Scale(inter.dNdy, dPdy, s);
	}

	bool SphereGeometry::SphereGeometry_ComputeIntersectionFunc(RayTraceSystem * system, DifferentialGeometry & inter, const Geometry * geom, const Ray & ray, const RayDifferential & rayDiffIn, float & t)
	{
		SphereGeometry * sphere = (SphereGeometry *)geom;

		float B = 2.0f * Vec3::Dot(ray.Origin, ray.Direction);
		float C = Vec3::Dot(ray.Origin, ray.Origin) - sphere->Rad2;
		float delta = B*B - 4.0f*C;
		if (delta < 0.0)
			return false;
		delta = sqrt(delta);
		float t1 = (-B - delta) * 0.5f;
		float t2 = (-B + delta) * 0.5f;
		if (t2 < ray.tMin) return false;
		if (t1 > ray.tMax) return false;
		if (t1 >= ray.tMin)
			t = t1;
		else
			t = t2;
		Vec3::Scale(inter.Position, ray.Direction, t);
		inter.Position += ray.Origin;
		Vec3::Normalize(inter.Normal, inter.Position);
		inter.Geometry = (Geometry*)geom;
		inter.UV.y = (asin(inter.Normal.y) + PI*0.5f) / PI;
		inter.UV.x = asin(inter.Normal.z) + PI/2;
		if (inter.Normal.x < 0.0)
			inter.UV.x = PI + acos(inter.Normal.x);
		inter.UV.x /= 2.0f*PI;
		inter.Material = geom->Material;
		return true;
	}

	bool SphereGeometry::SphereGeometry_ComputeIntersectionPredFunc(RayTraceSystem * system, const Geometry * geom, const Ray & ray)
	{
		SphereGeometry * sphere = (SphereGeometry *)geom;

		float B = 2.0f * Vec3::Dot(ray.Origin, ray.Direction);
		float C = Vec3::Dot(ray.Origin, ray.Origin) - sphere->Rad2;
		float delta = B*B - 4.0f*C;
		if (delta < 0.0)
			return false;
		delta = sqrt(delta);
		float t1 = (-B - delta) * 0.5f;
		float t2 = (-B + delta) * 0.5f;
		if (t2 < ray.tMin) return false;
		if (t1 > ray.tMax) return false;

		return true;
	}
}